package com.y.report.service.api;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.jq.orange.SqlMeta;
import com.jq.orange.engine.DynamicSqlEngine;
import com.y.report.cons.ApiConstant;
import com.y.report.entity.api.ApiRequestInfo;
import com.y.report.entity.api.ApiResponseInfo;
import com.y.report.entity.api.SqlExecInfo;
import com.y.report.entity.basic.YReportDs;
import com.y.report.entity.mapper.ApiReportInfo;
import com.y.report.entity.mapper.YSqlInfos;
import com.y.report.exception.ApiExecption;
import com.y.report.hepler.JdbcHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

@Service
public class ApiReportService {

    transient private static DynamicSqlEngine ENGINE = new DynamicSqlEngine();

    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private ApiArgsService apiArgsService;

    @Autowired
    private Environment environment;


    public Pair<Object, List<SqlExecInfo>> report(ApiReportInfo apiReportInfo, final Map<String, Object> sqlArgsMap) {
        List<SqlExecInfo> exec_statistics = new ArrayList<>();
        Map<String, Object> m = new LinkedHashMap<>();
        List<Map<String, Object>> l = new ArrayList<>();
        for (YSqlInfos x : apiReportInfo.getYSqlInfos()) {
            long start = System.currentTimeMillis();
            final SqlExecInfo sqlExecInfo = new SqlExecInfo(null, null, null, Collections.emptyList(), start, start, 0L);
            sqlExecInfo.setBeginTs(start);
            try {
                Object rt = reportFormat(Pair.of(x, execQuery(x, sqlArgsMap, (sqlTpl, sqlArgsTpl) -> {
                    sqlExecInfo.setSql(sqlTpl.getKey());
                    sqlExecInfo.setParseSql(sqlTpl.getValue());
                    sqlExecInfo.setSqlArgs(sqlArgsTpl.getKey());
                    sqlExecInfo.setParseSqlArgs(sqlArgsTpl.getValue());
                })));

                if (rt == null)
                    continue;
                else if (rt instanceof Map)
                    m.putAll((Map<String, Object>) rt);
                else if (rt instanceof List)
                    l.addAll((List) rt);

                String extArgsTemplate = JSON.toJSONString(rt, SerializerFeature.WriteMapNullValue);
                sqlArgsMap.putAll(apiArgsService.foreachParseArgs(extArgsTemplate, extArgsTemplate));
            } catch (Exception e) {
                throw new ApiExecption("api执行异常:" + e.getMessage());
            } finally {
                long end = System.currentTimeMillis();
                long elapsed = end - start;
                sqlExecInfo.setEngTs(end);
                sqlExecInfo.setElapsedTs(elapsed);
                exec_statistics.add(sqlExecInfo);
            }
        }

        Object result = l;
        if (CollUtil.isNotEmpty(m)) {
            result = m;
            if (CollUtil.isNotEmpty(l)) {
                m.put("__default", l);
            }
        }

        return Pair.of(result, exec_statistics);
    }

    private Object reportFormat(Pair<YSqlInfos, List<Map<String, Object>>> tpl) {
        //sql report format
        List<Map<String, Object>> dataList = tpl.getValue();
        String rtType = tpl.getKey().getYReportSql().getRtType();

        Object outobj = dataList;
        String prefix = StrUtil.subBefore(rtType, ":", false);
        if (StrUtil.equalsAnyIgnoreCase("map", prefix)) {
            outobj = CollUtil.isEmpty(dataList) ? Collections.emptyMap() : dataList.get(0);
        } else if (StrUtil.equalsAnyIgnoreCase("list", prefix)) {
            outobj = dataList;
        }
        String ks = StrUtil.subAfter(rtType, ":", false);
        if (StrUtil.isNotBlank(ks)) {
            final Object copyoutobj = outobj;
            outobj = Arrays.stream(ks.trim().split(","))
                    .map(String::trim)
                    .collect(Collectors.toList())
                    .stream()
                    .collect(LinkedHashMap::new, (x1, x2) -> x1.put(x2, copyoutobj), Map::putAll);
        }
        return outobj;
    }


    private List<Map<String, Object>> execQuery(YSqlInfos sqlInfo, final Map<String, Object> sqlArgsMap, BiConsumer<Pair<String, String>, Pair<Map<String, Object>, List<Object>>> cb) {

        if (sqlInfo != null && sqlInfo.getYReportDs() != null && sqlInfo.getYReportSql() != null) {
            YReportDs yRrportDs = sqlInfo.getYReportDs();
            Assert.notNull(yRrportDs.getDriverClassName(), "缺少jdbc驱动(driverClassName) !");

            Properties dbProp = new Properties();
            dbProp.setProperty("driverClassName", yRrportDs.getDriverClassName());
            Assert.notNull(yRrportDs.getJdbcUrl(), "缺少jdbc url !");
            dbProp.setProperty("jdbcUrl", yRrportDs.getJdbcUrl());
//            Assert.notNull(yRrportDs.getUsername(), "缺少jdbc username !");
            dbProp.setProperty("username", yRrportDs.getUsername());
//            Assert.notNull(yRrportDs.getPassword(), "缺少jdbc password !");
            dbProp.setProperty("password", yRrportDs.getPassword());

            String sqlTemplate = sqlInfo.getYReportSql().getSqlContent();
            Assert.notNull(sqlTemplate, "sql模板配置错误！");

            log.debug("parse sqlTemplate:【{}】, sqlArgs:【{}】", sqlTemplate, sqlArgsMap);
            //parse sql and sql_args
            final SqlMeta sqlMeta;
            try {
                sqlMeta = ENGINE.parse(sqlTemplate, sqlArgsMap);
            } catch (Throwable e) {
                throw new ApiExecption("api sql解析失败:" + e.getMessage());
            }
            //exec sql
            log.info("exec sql:【{}】, sqlArgs:【{}】, dataSource:【{}】", sqlMeta.getSql(), sqlMeta.getJdbcParamValues(), dbProp);
            final List<Map<String, Object>> rtResult;
            try {
                rtResult = JdbcHelper.execSql(dbProp, sqlMeta.getSql(), sqlMeta.getJdbcParamValues(), true, false);
            } catch (Exception e) {
                throw new ApiExecption("api sql执行失败:" + e.getMessage());
            } finally {
                if (cb != null)
                    cb.accept(Pair.of(sqlTemplate, sqlMeta != null ? sqlMeta.getSql() : null), Pair.of(sqlArgsMap, sqlMeta != null ? sqlMeta.getJdbcParamValues() : null));
            }
            return rtResult;
        }

        return null;
    }

    public ApiRequestInfo init() {
        String apiUrl = request.getRequestURI();
        apiUrl = StrUtil.removePrefix(apiUrl, environment.getProperty("server.servlet.context-path"));
        String apiMethod = request.getMethod();
        String apiName = request.getHeader(ApiConstant.API_HEADER_NAME_KEY);
        String apiCode = request.getHeader(ApiConstant.API_HEADER_CODE_KEY);
        String apiAuth = request.getHeader(ApiConstant.API_HEADER_AUTH_KEY);
        return new ApiRequestInfo(apiUrl, apiMethod, apiName, apiCode, apiAuth, null, null, null, null, null);
    }

    public void finish(ApiResponseInfo apiResponseInfo) throws IOException {
        Map<String, Object> body = apiResponseInfo.getRespBodyTemplate();
        Map<String, Object> headers = apiResponseInfo.getRespHeadersTemplate();
        Map<String, Object> cookies = apiResponseInfo.getRespCookiesTemplate();
        String status = apiResponseInfo.getStatus();

        log.debug("response info: \nstatus -> [{}]\nbody -> [{}]\nheaders -> [{}]\ncookies -> [{}]", status, body, headers, cookies);
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json");
        if (MapUtil.isNotEmpty(body))
            headers.entrySet()
                    .stream()
                    .filter(x -> StrUtil.isNotBlank(x.getKey()) && x.getValue() != null && StrUtil.isNotBlank(x.getValue().toString()))
                    .forEach((x -> response.setHeader(x.getKey(), x.getValue().toString())));

        if (MapUtil.isNotEmpty(cookies))
            cookies.entrySet()
                    .stream()
                    .filter(x -> StrUtil.isNotBlank(x.getKey()) && x.getValue() != null && StrUtil.isNotBlank(x.getValue().toString()))
                    .forEach((x -> response.addCookie(new Cookie(x.getKey(), x.getValue().toString()))));

        if (status != null && NumberUtil.isInteger(status))
            response.setStatus(Integer.parseInt(status));

        try (PrintWriter writer = response.getWriter()) {
            //隐藏key
            PropertyFilter profilter = new PropertyFilter() {
                @Override
                public boolean apply(Object o, String k, Object v) {
                    if (k != null && k.startsWith(ApiConstant.API_RESPONSE_HIDE_PREFIX)) {
                        return false;
                    }
                    return true;
                }
            };
            writer.write(JSON.toJSONString(body, profilter, SerializerFeature.WriteMapNullValue));
        } catch (IOException e) {
            throw e;
        }

    }

}
